Use JBSMATCH opcode for ethernet address matching This commit refactors the code to use the JBSMATCH opcode when possible for ethernet address matching, improving code density. Before: 03-21 14:43:07.523 28553 28566 I ApfFilterTest: all feature on, program size: 4503 After: 03-21 16:43:22.941 1490 1510 I ApfFilterTest: all feature on, program size: 4446 Test: TH Change-Id: Id2f20bdfe2da579b3f1d8e441533532aaee2816d 
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 7c6692f..bde487e 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java 
@@ -1830,8 +1830,8 @@    // Pass if non-broadcast reply.  // This also accepts multicast arp, but we assume those don't exist. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_ARP_UNICAST_REPLY); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_ARP_UNICAST_REPLY);    // It is a broadcast reply.  if (mIPv4Address == null) { @@ -1897,8 +1897,8 @@  // the future, such packets will likely be dropped by multicast filters.  // Since the device may have packet forwarding enabled, APF needs to pass any received  // unicast IPv4 ping not destined for the device's IP address to the kernel. - gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipIpv4PingFilter) + gen.addJumpIfBytesAtOffsetNotEqual( + ETH_DEST_ADDR_OFFSET, mHardwareAddress, skipIpv4PingFilter)  .addLoadImmediate(R0, IPV4_DEST_ADDR_OFFSET)  .addJumpIfBytesAtR0NotEqual(mIPv4Address, skipIpv4PingFilter);   @@ -1979,11 +1979,11 @@  // address for IPv4 mDNS packet) or the device's MAC address, skip filtering.  // We need to check both the mDNS multicast MAC address and the device's MAC address  // because multicast to unicast conversion might have occurred. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf( - List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS), - skipMdnsFilter - ); + gen.addJumpIfBytesAtOffsetEqualsNoneOf( + ETH_DEST_ADDR_OFFSET, + List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS), + skipMdnsFilter + );    // Ignore packets with IPv4 options (header size not equal to 20) as they are rare.  gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE) @@ -2185,8 +2185,8 @@  // Otherwise, this is an IPv4 unicast, pass  // If L2 broadcast packet, drop.  // TODO: can we invert this condition to fall through to the common pass case below? - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_IPV4_UNICAST); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_IPV4_UNICAST);  gen.addCountAndDrop(DROPPED_IPV4_L2_BROADCAST);  }   @@ -2329,8 +2329,8 @@  // used by processes other than clatd. This is because APF cannot reliably detect signal  // on when IPV6_{JOIN,LEAVE}_ANYCAST is triggered.  final List<byte[]> allMACs = getKnownMacAddresses(); - v6Gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0EqualsNoneOf(allMACs, DROPPED_IPV6_NS_OTHER_HOST); + v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(ETH_DEST_ADDR_OFFSET, allMACs, + DROPPED_IPV6_NS_OTHER_HOST);    // Dst IPv6 address check:  final List<byte[]> allSuffixes = getSolicitedNodeMcastAddressSuffix(allIPv6Addrs); @@ -2444,11 +2444,11 @@  // address for IPv6 mDNS packet) or the device's MAC address, skip filtering.  // We need to check both the mDNS multicast MAC address and the device's MAC address  // because multicast to unicast conversion might have occurred. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf( - List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), - skipMdnsFilter - ); + gen.addJumpIfBytesAtOffsetEqualsNoneOf( + ETH_DEST_ADDR_OFFSET, + List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), + skipMdnsFilter + );    // Skip filtering if the packet is not an IPv6 UDP packet.  gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET) @@ -2509,8 +2509,8 @@  true /* includeNonTentative */,  false /* includeTentative */,  false /* includeAnycast */); - gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipPing6Offload) + gen.addJumpIfBytesAtOffsetNotEqual( + ETHER_DST_ADDR_OFFSET, mHardwareAddress, skipPing6Offload)  .addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET)  .addJumpIfBytesAtR0EqualsNoneOf(nonTentativeIPv6Addrs, skipPing6Offload);   @@ -3604,8 +3604,8 @@  // Pass unicast TDLS packet but drop non-unicast TDLS packet.  short skipTDLScheck = gen.getUniqueLabel();  gen.addJumpIfR0NotEquals(0x890DL, skipTDLScheck) - .addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0NotEqual(mHardwareAddress, DROPPED_NON_UNICAST_TDLS) + .addCountAndDropIfBytesAtOffsetNotEqual( + ETH_DEST_ADDR_OFFSET, mHardwareAddress, DROPPED_NON_UNICAST_TDLS)  .addCountAndPass(PASSED_NON_IP_UNICAST)  .defineLabel(skipTDLScheck);   @@ -3650,8 +3650,8 @@  gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);    // Drop non-IP non-ARP broadcasts, pass the rest - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_NON_IP_UNICAST); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_NON_IP_UNICAST);  gen.addCountAndDrop(DROPPED_ETH_BROADCAST);    // Add IPv6 filters: @@ -3739,11 +3739,17 @@  }    void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException { + final List<byte[]> preloadedMacAddress = getKnownMacAddresses();  final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */,  true /* includeTentative */, true /* includeAnycast */); - final int preloadDataSize = preloadedIPv6Address.size() * 16; + final int preloadDataSize = + preloadedIPv6Address.size() * 16 + preloadedMacAddress.size() * 6;  final byte[] preloadData = new byte[preloadDataSize];  int offset = 0; + for (byte[] addr : preloadedMacAddress) { + System.arraycopy(addr, 0, preloadData, offset, 6); + offset += 6; + }  for (byte[] addr : preloadedIPv6Address) {  System.arraycopy(addr, 0, preloadData, offset, 16);  offset += 16; 
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java index f142e31..fb929dc 100644 --- a/src/android/net/apf/ApfV4GeneratorBase.java +++ b/src/android/net/apf/ApfV4GeneratorBase.java 
@@ -482,6 +482,17 @@  }    /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0NotEqual(bytes, tgt); + } + + /**  * Add instructions to the end of the program to increase counter and drop packet if the  * bytes of the packet at an offset specified by register0 don't match {@code bytes}.  * WARNING: may modify R1 @@ -499,6 +510,28 @@    /**  * Add instructions to the end of the program to increase counter and drop packet if the + * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0NotEqual(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and pass packet if the + * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0NotEqual(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and drop packet if the  * bytes of the packet at an offset specified by register0 match {@code bytes}.  * WARNING: may modify R1  */ 
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java index 014d893..c686b71 100644 --- a/src/android/net/apf/ApfV61GeneratorBase.java +++ b/src/android/net/apf/ApfV61GeneratorBase.java 
@@ -325,6 +325,24 @@  return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel());  }   + @Override + public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpDropLabel()); + } + + @Override + public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt) + throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), tgt); + } +  /**  * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP  * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype 
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java index 045423b..07bd191 100644 --- a/src/android/net/apf/ApfV6Generator.java +++ b/src/android/net/apf/ApfV6Generator.java 
@@ -262,6 +262,18 @@  }    @Override + public ApfV6Generator addJumpIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt); + } + + @Override + public ApfV6Generator addJumpIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt); + } + + @Override  public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,  ApfCounterTracker.Counter cnt) throws IllegalInstructionException {  if (values.isEmpty()) { 
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java index 68a29c5..90f0a28 100644 --- a/src/android/net/apf/ApfV6GeneratorBase.java +++ b/src/android/net/apf/ApfV6GeneratorBase.java 
@@ -552,6 +552,21 @@  return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */);  }   + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesSet}. + */ + public abstract Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesSet}. + */ + public abstract Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;    /**  * Check if the byte is valid dns character: A-Z,0-9,-,_,%,@ 
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt index 2d20ed6..6fcf641 100644 --- a/tests/unit/src/android/net/apf/ApfFilterTest.kt +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt 
@@ -22,6 +22,8 @@  import android.net.MacAddress  import android.net.NattKeepalivePacketDataParcelable  import android.net.TcpKeepalivePacketDataParcelable +import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS +import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS  import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4  import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST  import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST @@ -240,6 +242,8 @@  intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(),  // 33:33:ff:bb:cc:dd  intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), + ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, + ETH_MULTICAST_MDNS_V6_MAC_ADDRESS  )    // Using scapy to generate payload: @@ -5797,7 +5801,8 @@  )  assertThat(program.size).isLessThan(apfRamSize + 1)  assertThat(program).isNotEqualTo(ByteArray(apfRamSize) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64)  apfRamSize += step  }  } @@ -5817,7 +5822,8 @@  val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize()  assertThat(program.size).isLessThan(availableRam + 1)  assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64)  apfRamSize += step  }  } @@ -5836,7 +5842,8 @@  val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize()  assertThat(program.size).isLessThan(availableRam + 1)  assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64)  apfRamSize += step  }  }